// 把 对象数据类型 转化成 查询字符串 格式
function queryStringify(obj) {
  // 目的就是把 obj 转换成 查询字符串
  // 1. 准备一个字符串
  let str = ''

  // 2. 向字符串上拼接一系列内容
  for (let k in obj) {
    // 一条数据是 key=value
    // key 就是每一次循环的 k
    // value 就是每一次循环的 obj[k]
    str += `${ k }=${ obj[k] }&`
  }

  // 3. 把字符串返回
  return str.slice(0, -1)
}

// 封装 ajax

/*
  1. 验证 options
    + 因为我们规定了, 必须要以对象的方式传递所有参数
    + 如果他调用的时候, 传递的不是对象数据类型
    + 主要你不是对象数据类型, 直接返回, 不需要后续代码
    + 准确检测所有数据类型的方法
      => 语法: Object.prototype.toString.call(你要检测的数据类型)
      => 返回值: 字符串类型, '[object 数据类型]'
*/

/*
  2. 验证参数数据格式
    2-1. 验证 url 必填
      + 如果你的 options 内没有 url 参数, 那么直接手动抛出异常
      + 必须是一个 字符串类型
    2-2. 验证 method
      + 可以不写 undefined, 可以是 get 可以是 post
      + 其他的暂时我们不接受
    2-3. 验证 async
      + 可以不写, undefined, 只要你写了, 必须是一个 布尔值
      + 其他的数据类型不行
    2-4. 验证 data
      + 可以不写, 只要你写了, 就得是一个 字符串类型 或者 对象数据类型
    2-5. 验证 headers
      + 可以不写, 只要你写了, 就得是一个 对象数据类型
    2-6. 验证 dataType
      + 可以不写, 只要你写了, 必须是 'string' 或者 'json'
    2-7. 验证 success
      + 可以不写, 只要你写了, 必须是一个 function 数据类型
    2-8. 验证 error
      + 可以不写, 只要你写了, 必须是一个 function 数据类型
*/

/*
  3. 准备一个默认值
*/

/*
  4. 按照 ajax 的步骤发送请求
    + 使用的就是 _default 内的数据
*/
// function ajax(options) {
//   console.log(options)
//   // 1. 验证 options 必须是一个对象数据类型
//   if (Object.prototype.toString.call(options) !== '[object Object]') {
//     // 不需要后续的操作了
//     // 手动抛出异常
//     // 语法: throw new Error(错误信息)
//     throw new Error('"options" 需要传递一个 Object 类型数据 ^_^')
//   }

//   // 代码能执行到这里, 说明 options 必然是一个对象数据类型
//   // 从对象数据类型内获取出来 url 信息进行验证
//   // 解构赋值, 快速从对象中获取成员
//   // 语法: const { 键名 } = 对象
//   const { url, method, async, data, headers, dataType, success, error } = options

//   // 2. 验证参数格式
//   // 2-1. url
//   if (!url || typeof url !== 'string') {
//     // 表示 url 没有填写, 或者填写的空字符
//     throw new Error('"url" 需要填写一个 String 类型数据 O(∩_∩)O哈哈~')
//   }

//   // 2-2. method
//   if (!(method === undefined || /^(get|post)$/i.test(method))) {
//     // 代码能来到这里, 表示 method 既不是 undefined , 也不是 get 或者 post
//     throw new Error('目前我只接受 "GET" 和 "POST" 请求, 请期待更新 (#^.^#)')
//   }

//   // 2-3. async
//   if (!(async === undefined || typeof async === 'boolean')) {
//     // 代码能来到这里, 说明你的 async 既不是 undefined 也不是 布尔值
//     throw new Error('"async" 需要传递一个布尔值哦 o(*￣︶￣*)o')
//   }

//   // 2-4. data
//   if (!(data === undefined || typeof data === 'string' || Object.prototype.toString.call(data) === '[object Object]')) {
//     // 代码能来到这里, 说明 data 既不是 undefiend 也是不是 对象 也不是 字符串
//     throw new Error('"data" 需要传递一个 String 或者 Object 类型数据')
//   }

//   // 2-5. headers
//   if (!(headers === undefined || Object.prototype.toString.call(headers) === '[object Object]')) {
//     throw new Error('"headers" 需要传递一个 Object 数据类型')
//   }

//   // 2-6. dataType
//   if (!(dataType === undefined || /^(string|json)$/i.test(dataType))) {
//     throw new Error('"dataType" 需要传递一个 "string" 或者 "json"')
//   }

//   // 2-7. success
//   if (!(success === undefined || typeof success === 'function')) {
//     throw new Error('"success" 需要传递一个 Function 数据类型')
//   }

//   // 2-7. error
//   if (!(error === undefined || typeof error === 'function')) {
//     throw new Error('"success" 需要传递一个 Function 数据类型')
//   }

//   // 代码能执行到这里, 说明所有的参数都是满足格式的
//   // 我就按照参数来进行操作
//   // 3. 准备一套默认值
//   const _default = {
//     // 代码能执行到这里, 说明 url 肯定有信息
//     url: url,
//     // 代码能执行到这里, method 要么是 undefined, 要么是 get 要么是 post
//     method: method || 'GET',
//     // 代码能执行到这里, async 要么是 undefined, 要么是 true 要么是 false
//     // async: typeof async === 'boolean' ? async : true,
//     // ES2020 提出的新运算符, 叫做 空值运算, ??
//     // 只有当符号前面是 空(undefined || null) 的时候, 才会用后面的
//     async: async ?? true,
//     // 代码能执行到这里, 说明 data 要么是 undefined 要么是 对象, 要么是 字符串
//     data: data || '',
//     // 代码能执行到这里, 说明 headers 要么是 undefined, 要么是一个 对象数据类型
//     // ...对象的时候, 就是把对象数据类型的 {} 去掉
//     headers: { 'content-type': 'application/x-www-form-urlencoded', ...headers },
//     // 代码能执行到这里, 说明 dataType 要么是 undefined, 要么是 'string' 要么是 'json'
//     dataType: dataType || 'string',
//     // 代码能执行到这里, 说明 success 要么是 undefined, 要么是 函数
//     success: success || function () {},
//     error: error || function () {}
//   }
//   // 3-2. 默认值的操作
//   // 操作一下 data
//   // 因为代码来到这里, data 要么是 字符串 要么就是 对象
//   // 因为你将来发送请求的时候, data 肯定要是个字符串类型
//   // 我们直接在这里判断, 如果 data 是 对象数据类型, 那么我们直接转换成查询字符串格式
//   if (typeof data === 'object') {
//     // 把 data 转换成查询字符串格式
//     _default.data = queryStringify(data)
//   }

//   // 操作一下 url
//   // 代码能执行到这里, 说明 data 必然是一个 字符串类型了
//   // 如果你是 get 请求, 并且 data 不是一个 空字符串
//   // 你将来发送请求应该是 /xxxx?参数
//   // 直接拼接好
//   if (/^get$/i.test(_default.method) && data) {
//     // 代码能执行到这里, 说明 请求方式是 get, 并且有需要携带到后端的参数
//     _default.url += '?' + _default.data
//   }

//   console.log('默认值 : ', _default)

//   // 4. 发送请求
//   const xhr = new XMLHttpRequest()

//   xhr.open(_default.method, _default.url, _default.async)

//   // 接受响应的位置
//   xhr.onload = function () {
//     // 有的请求服务器返回的是 普通字符串
//     // 有的请求服务器返回的是 json 格式字符串

//     // _default.dataType 只是决定我是否执行一个 JSON.parse() 而已
//     // 如果解析失败, 表示你本来不是 json 格式字符串, 但是你执行了 JSON.parse()
//     //   执行失败的回调函数
//     // 否则, 执行 成功的回调函数

//     let result = xhr.responseText
//     if (_default.dataType === 'json') {
//       // 执行解析
//       try {
//         result = JSON.parse(xhr.responseText)
//         _default.success(result)
//       } catch(err) {
//         _default.error('解析失败 ! 因为后端返回的结果不是 json 格式字符串, 请查证后再试 !!!')
//       }

//     } else {
//       // 不执行解析
//       _default.success(result)
//     }





//     // 不要了
//     // let result = xhr.responseText
//     // if (_default.dataType === 'json') result = JSON.parse(xhr.responseText)

//     // // 请求成功以后, 调用你传递进来的 success 函数
//     // _default.success(result)
//   }

//   // 不需要管请求方式, 只要把 _default 内的 headers 所有的内容都设置到请求头内就可以了
//   for (let k in _default.headers) {
//     // k 就是 每一个键名, 也就是我要设置的请求头中的 key
//     // _default.headers[k] 就是每一个值, 也就是我要设置的请求头中的 value
//     // console.log(k, ' ---- ', _default.headers[k])
//     // 设置请求头
//     xhr.setRequestHeader(k, _default.headers[k])
//   }

//   // 判断一下如果是 post 请求, 小括号内带个请求体
//   // 如果是 get 请求, 什么也不用携带
//   if (/^get$/i.test(_default.method)) {
//     xhr.send()
//   } else {
//     xhr.send(_default.data)
//   }
// }


/*
  设置请求头信息的操作

  get 请求
    + authorization, 有的时候需要, 有的时候不需要
    + content-type, 不需要, 但是如果你设置成 application/x-www-form-urlencoded 也不会出现问题

  post 请求
    + authorization, 有的时候需要, 有的时候不需要
    + content-type, 需要设置, 根据请求体格式进行设置

  准备一个 对象数据类型
    {
      'content-type': 'application/x-www-form-urlencoded',
      authorization: 'asdasdasd.asdasd.asdasd'
    }
    + 如果对象内没有东西, 那么你默认值是帮我设置一个 content-type 就可以了
*/




// 封装 ajax
function ajax(options) {
  // 1. 验证你传递进来的整体内容必须是一个对象数据类型
  if (Object.prototype.toString.call(options) !== '[object Object]') throw new Error('"options" 需要传递一个 Object 类型数据 ^_^')

  // 取出 options 内的所有选项, 进行一一的格式验证
  const { url, method, async, data, headers, dataType, success, error } = options

  // 2. 验证参数格式
  // 2-1. url
  if (!url || typeof url !== 'string') throw new Error('"url" 需要填写一个 String 类型数据 O(∩_∩)O哈哈~')
  // 2-2. method
  if (!(method === undefined || /^(get|post)$/i.test(method))) throw new Error('目前我只接受 "GET" 和 "POST" 请求, 请期待更新 (#^.^#)')
  // 2-3. async
  if (!(async === undefined || typeof async === 'boolean')) throw new Error('"async" 需要传递一个布尔值哦 o(*￣︶￣*)o')
  // 2-4. data
  if (!(data === undefined || typeof data === 'string' || Object.prototype.toString.call(data) === '[object Object]')) throw new Error('"data" 需要传递一个 String 或者 Object 类型数据')
  // 2-5. headers
  if (!(headers === undefined || Object.prototype.toString.call(headers) === '[object Object]')) throw new Error('"headers" 需要传递一个 Object 数据类型')
  // 2-6. dataType
  if (!(dataType === undefined || /^(string|json)$/i.test(dataType))) throw new Error('"dataType" 需要传递一个 "string" 或者 "json"')
  // 2-7. success
  if (!(success === undefined || typeof success === 'function')) throw new Error('"success" 需要传递一个 Function 数据类型')
  // 2-8. error
  if (!(error === undefined || typeof error === 'function')) throw new Error('"success" 需要传递一个 Function 数据类型')

  // 3. 准备一套默认值
  const _default = {
    url: url,
    method: method || 'GET',
    async: async ?? true,
    data: data || '',
    headers: { 'content-type': 'application/x-www-form-urlencoded', ...headers },
    dataType: dataType || 'string',
    success: success || function () {},
    error: error || function () {}
  }
  // 3-2. 默认值的操作
  // 操作 data 如果是对象数据类型, 转换成 查询字符串
  if (typeof data === 'object') _default.data = queryStringify(data)
  // 如果是 get 请求, 并且有参数, 那么直接组装一下 url 信息
  if (/^get$/i.test(_default.method) && data) _default.url += '?' + _default.data

  // 4. 发送请求
  const xhr = new XMLHttpRequest()
  xhr.open(_default.method, _default.url, _default.async)
  xhr.onload = function () {
    if (_default.dataType === 'string') return _default.success(xhr.responseText)

    // 执行解析
    try {
      let result = JSON.parse(xhr.responseText)
      _default.success(result)
    } catch(err) {
      _default.error('解析失败 ! 因为后端返回的结果不是 json 格式字符串, 请查证后再试 !!!')
    }
  }

  // 设置请求头内的信息
  for (let k in _default.headers) xhr.setRequestHeader(k, _default.headers[k])
  ;/^get$/i.test(_default.method)  ? xhr.send() : xhr.send(_default.data)
}


// promise 的形式封装 ajax
function pAjax(options) {
  const p = new Promise((resolve, reject) => {
    ajax({
      url: options.url,
      data: options.data,
      async: options.async,
      method: options.method,
      headers: options.headers,
      dataType: options.dataType,
      success (res) {
        // 表示本次 ajax 成功了
        resolve(res)
      },
      error (err) {
        // 表示本次 ajax 失败了
        reject(err)
      }
    })
  })

  return p
}
